package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @SneakyThrows
    @Override
    public List<Map<String, Object>> channel(Long c1Id) {
        // 1. 根据一级分类id 查询该一级分类下7个置顶的三级分类集合
        Result<List<BaseCategory3>> category3ListByC1Id = albumInfoFeignClient.getBaseCategory3ListByC1Id(c1Id);
        List<BaseCategory3> baseCategory3ListData = category3ListByC1Id.getData();
        if (CollectionUtils.isEmpty(baseCategory3ListData)) {
            throw new GuiguException(201, "远程调用分类信息失败");
        }

        // 2.过滤获取到三级分类的id集合 而且转成FieldValue类型
        List<FieldValue> c3IdFieldValue = baseCategory3ListData.stream().map(baseCategory3 -> {
            return FieldValue.of(baseCategory3.getId());
        }).toList();

        // 3.再将三级分类集合转成一个Map
        Map<Long, BaseCategory3> baseCategory3Map = baseCategory3ListData.stream().collect(Collectors.toMap(BaseCategory3::getId, v -> v));

        // 4.构建dsl语句
        SearchRequest searchRequest = buildChannelDsl(c3IdFieldValue);

        // 5.查询es
        SearchResponse<AlbumInfoIndex> response;
        try {
            response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        } catch (Exception e) {
            log.error("查询ES失败", e);
            throw new GuiguException(202, "查询ES失败");
        }

        // 6.解析es的返回数据
        List<Map<String, Object>> result = parseChannelData(response, baseCategory3Map);

        return result;
    }

    @SneakyThrows
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        // 1. 构建带条件的查询dsl语句
        SearchRequest searchRequest = buildSearchDsl(albumIndexQuery);
        // 2. 查询es得到响应对象
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        // 3. 解析es的返回数据
        AlbumSearchResponseVo albumSearchResponseVo = parseSearchData(response);
        Integer pageSize = albumIndexQuery.getPageSize();
        albumSearchResponseVo.setPageSize(pageSize);
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        Long total = albumSearchResponseVo.getTotal();
        Long totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        albumSearchResponseVo.setTotalPages(totalPage);
        // 4. 返回结果给前端
        return albumSearchResponseVo;
    }

    @SneakyThrows
    @Override
    public Set<String> completeSuggest(String keyword) {
        //1. 构建dsl语句
        SearchRequest searchRequest = buildCompletionDsl(keyword);
        //2. 开始查询
        SearchResponse<SuggestIndex> response = elasticsearchClient.search(searchRequest, SuggestIndex.class);
        //3. 解析数据
        Set<String> set = parseCompletionData(response);
        if (set.size() < 10) {
            // 不满10个时,从包含里面进行补充到10个
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(sb -> sb.index("suggestinfo").query(qb -> qb.match(mqb -> mqb.field("title").query(keyword))), SuggestIndex.class);
            for (Hit<SuggestIndex> hit : searchResponse.hits().hits()) {
                SuggestIndex suggestIndex = hit.source();
                String title = suggestIndex.getTitle();
                set.add(title);
                if (set.size() >= 10) {
                    break;
                }
            }
        }
        //4. 返回
        return set;
    }

    private SearchRequest buildCompletionDsl(String keyword) {

        // 1.创建SearchRequestBuilder对象
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        // 2. 创建SuggesterBuilder对象
        Suggester.Builder suggestBuilder = new Suggester.Builder();
        // 3. 拼接提示词的dsl
        suggestBuilder
                .suggesters("suggestionKeyword", fsb -> fsb
                        .prefix(keyword)
                        .completion(csb -> csb.field("keyword")))
                .suggesters("suggestionKeywordPinyin", fsb -> fsb
                        .prefix(keyword)
                        .completion(csb -> csb.field("keywordPinyin")))
                .suggesters("suggestionKeywordSequence", fsb -> fsb
                        .prefix(keyword)
                        .completion(csb -> csb.field("keywordSequence")));
        // 4. 得到suggester
        Suggester suggester = suggestBuilder.build();
        SearchRequest searchRequest = searchRequestBuilder.index("suggestinfo").suggest(suggester).build();
        System.out.println("提示词的dsl:" + searchRequest);
        return searchRequest;
    }

    private Set<String> parseCompletionData(SearchResponse<SuggestIndex> response) {
        HashSet<String> result = new HashSet<>();
        // Map中有三对key,value
        // 第一对：key：suggestionKeyword    value：List<满足条件的内容>
        // 第二对：key：suggestionKeywordPinyin    value：List<满足条件的内容>
        // 第三对：key：suggestionKeywordSequence    value：List<满足条件的内容>
        Map<String, List<Suggestion<SuggestIndex>>> map = response.suggest();
        for (Map.Entry<String, List<Suggestion<SuggestIndex>>> stringListEntry : map.entrySet()) {
            List<Suggestion<SuggestIndex>> value = stringListEntry.getValue();
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : value) {
                List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
                for (CompletionSuggestOption<SuggestIndex> option : options) {
                    SuggestIndex suggestIndex = option.source();
                    String title = suggestIndex.getTitle();
                    result.add(title);
                }
            }
        }
        return result;
    }

    /**
     * 构建带条件的查询dsl语句
     *
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest buildSearchDsl(AlbumIndexQuery albumIndexQuery) {

        // 1.构建最外层{}
        SearchRequest.Builder searchBuilder = new SearchRequest.Builder();

        // 2.构建一个bool的查询bool查询的{}
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        // 3.用boolQueryBuilder进行查询(should以及must)
        // 3.1 判断关键字是否携带
        String keyword = albumIndexQuery.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumTitle").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumIntro").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("announcerName").query(keyword)));
        }

        // 3.2 判断分类id
        // a)一级分类id是否为空
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category1Id").value(category1Id)));
        }
        // b)二级分类id是否为空
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category2Id").value(category2Id)));
        }
        // c)三级分类id是否为空
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category3Id").value(category3Id)));
        }

        // 3.3 判断标签是否携带了
        // String:"1:2" "2:4"
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            for (String attrAndValueId : attributeList) {
                String[] split = attrAndValueId.split(":");
                String attrId = split[0];  // 属性id
                String valueId = split[1]; // 属性值id

                // 创建NestedQueryBuilder对象
                NestedQuery.Builder builder = new NestedQuery.Builder();
                builder.path("attributeValueIndexList")
                        .query(qb -> qb
                                .bool(bqb -> bqb
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.attributeId")
                                                        .value(attrId)))
                                        .must(mqb -> mqb
                                                .term(tqb -> tqb
                                                        .field("attributeValueIndexList.valueId")
                                                        .value(valueId)))));
                NestedQuery nestedQuery = builder.build();
                boolQueryBuilder.must(qb -> qb.nested(nestedQuery));
            }
        }

        // 4.构建boolQuery
        BoolQuery boolQuery = boolQueryBuilder.build();
        // boolQuery --> query对象
        Query query = boolQuery._toQuery();
        searchBuilder.index("albuminfo").query(query);

        // 5.判断分页
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        Integer from = (pageNo - 1) * pageSize;
        searchBuilder.from(from).size(pageSize);

        // 6.判断排序 (1:desc[asc]) (2:desc[asc]) (3:desc[asc])
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] split = order.split(":");
            String orderDimension = split[0];
            String rule = split[1];

            SortOrder sortOrder = rule.equals("desc") ? SortOrder.Desc : SortOrder.Asc;
            switch (orderDimension) {
                case "1":
                    // 热度值
                    searchBuilder.sort(sob -> sob.field(fsb -> fsb.field("hotScore").order(sortOrder)));
                    break;
                case "2":
                    // 购买量
                    searchBuilder.sort(sob -> sob.field(fsb -> fsb.field("buyStatNum").order(sortOrder)));
                    break;
                case "3":
                    // 发布时间
                    searchBuilder.sort(sob -> sob.field(fsb -> fsb.field("createTime").order(sortOrder)));
            }
        } else { // 默认热度值降序排序
            searchBuilder.sort(sob -> sob.field(fsb -> fsb.field("hotScore").order(SortOrder.Desc)));
        }
        // 7.组合高亮
        searchBuilder.highlight(hob -> hob
                .preTags("<span style='color:red'>")
                .postTags("</span>")
                .fields("albumTitle", fob -> fob.numberOfFragments(1))
                .fields("albumIntro", fob -> fob.numberOfFragments(1)));

        // 返回SearchRequest对象
        SearchRequest searchRequest = searchBuilder.build();
        System.out.println("搜索的dsl语句" + searchRequest.toString());
        return searchRequest;
    }


    /**
     * 解析es的返回数据
     *
     * @param response
     * @return
     */
    private AlbumSearchResponseVo parseSearchData(SearchResponse<AlbumInfoIndex> response) {
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        TotalHits totalHits = response.hits().total();
        long total = totalHits.value();
        // 总专辑数
        albumSearchResponseVo.setTotal(total);

        List<AlbumInfoIndexVo> albumInfoIndexVoList = response.hits().hits().stream().map(albumInfoIndexHit -> {
                    AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
                    AlbumInfoIndex albumInfoIndex = albumInfoIndexHit.source();
                    BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
                    // 处理高亮
                    Map<String, List<String>> highlight = albumInfoIndexHit.highlight();
                    List<String> albumTitle = highlight.get("albumTitle");
                    if (!CollectionUtils.isEmpty(albumTitle)) {
                        String highlightAlbumTitle = albumTitle.get(0);
                        albumInfoIndexVo.setAlbumTitle(highlightAlbumTitle);
                    }
                    List<String> albumIntro = highlight.get("albumIntro");
                    if (!CollectionUtils.isEmpty(albumIntro)) {
                        String highlightAlbumIntro = albumIntro.get(0);
                        albumInfoIndexVo.setAlbumIntro(highlightAlbumIntro);
                    }
                    return albumInfoIndexVo;
                }
        ).toList();
        // 专辑集合
        albumSearchResponseVo.setList(albumInfoIndexVoList);
        return albumSearchResponseVo;
    }

    /**
     * 解析es的返回数据
     *
     * @param response
     * @param baseCategory3Map
     * @return
     */
    private List<Map<String, Object>> parseChannelData(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> baseCategory3Map) {

        ArrayList<Map<String, Object>> result = new ArrayList<>();

        // 1.得到三级分类聚合结果
        Aggregate category3IdAgg = response.aggregations().get("category3IdAgg");

        // 2.得到Aggregate具体的类型
        LongTermsAggregate longTermsAggregate = category3IdAgg.lterms();

        // 3.得到父聚合的桶
        List<LongTermsBucket> buckets = longTermsAggregate.buckets().array();

        for (LongTermsBucket bucket : buckets) {
            // 得到子聚合
            TopHitsAggregate topHitHotsScoreAgg = bucket.aggregations().get("topHitHotsScoreAgg").topHits();
            ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();
            for (Hit<JsonData> hit : topHitHotsScoreAgg.hits().hits()) {
                JsonData source = hit.source();
                AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(source.toString(), AlbumInfoIndex.class);
                albumInfoIndices.add(albumInfoIndex);
            }


            HashMap<String, Object> map = new HashMap<>();
            long category3Id = bucket.key();
            map.put("baseCategory3", baseCategory3Map.get(category3Id));
            map.put("list", albumInfoIndices);
            result.add(map);
        }

        return result;
    }

    /**
     * 构建dsl语句
     * 查询当前以一级分类下的三级分类集合中每一个三级分类对象中的6个热度值搞的专辑
     *
     * @param c3IdsFieldValue
     * @return
     */
    private SearchRequest buildChannelDsl(List<FieldValue> c3IdsFieldValue) {

        // 1.构建SearchRequestBuilder对象
        SearchRequest.Builder builder = new SearchRequest.Builder();

        builder.index("albuminfo")
                .query(b -> b.terms(
                        tqb -> tqb.field("category3Id").terms(
                                tqfb -> tqfb.value(c3IdsFieldValue))))
                .aggregations("category3IdAgg",
                        ab -> ab.terms(
                                        tab -> tab.field("category3Id").size(7))
                                .aggregations("topHitHotsScoreAgg",
                                        sab -> sab.topHits(
                                                thab -> thab.size(6).sort(
                                                        sob -> sob.field(
                                                                fsb -> fsb.field("hotScore").order(SortOrder.Desc))
                                                )
                                        )
                                )
                );
        SearchRequest searchRequest = builder.build();
        System.out.println("频道页的dsl:" + searchRequest.toString());
        // 3.返回SearchRequest对象
        return searchRequest;
    }
}
